mod using_trait_objects {
    pub trait Migration {
        fn execute(&self) -> &str;

        fn rollback(&self) -> &str;
    }

    pub struct CreateTable;

    impl Migration for CreateTable {
        fn execute(&self) -> &str {
            "create table"
        }

        fn rollback(&self) -> &str {
            "drop table"
        }
    }

    pub struct AddField;

    impl Migration for AddField {
        fn execute(&self) -> &str {
            "add field"
        }

        fn rollback(&self) -> &str {
            "remove field"
        }
    }

    pub struct Schema {
        commands: Vec<Box<dyn Migration>>,
    }

    impl Schema {
        pub fn new() -> Self {
            Self { commands: vec![] }
        }

        pub fn add_migration(&mut self, command: Box<dyn Migration>) {
            self.commands.push(command);
        }

        pub fn execute(&self) -> Vec<&str> {
            self.commands
                .iter()
                .map(|command| command.execute())
                .collect()
        }

        pub fn rollback(&self) -> Vec<&str> {
            // Commands are rolled back in the reverse direction.
            self.commands
                .iter()
                .rev()
                .map(|command| command.rollback())
                .collect()
        }
    }
}

mod using_function_pointers {
    type FnPtr = fn() -> String;

    pub struct Command {
        execute: FnPtr,
        rollback: FnPtr,
    }

    pub struct Schema {
        commands: Vec<Command>,
    }

    impl Schema {
        pub fn new() -> Self {
            Self { commands: vec![] }
        }

        pub fn add_migration(&mut self, execute: FnPtr, rollback: FnPtr) {
            self.commands.push(Command { execute, rollback });
        }

        pub fn execute(&self) -> Vec<String> {
            self.commands
                .iter()
                .map(|command| (command.execute)())
                .collect()
        }

        pub fn rollback(&self) -> Vec<String> {
            // Commands are rolled back in the reverse direction.
            self.commands
                .iter()
                .rev()
                .map(|command: &Command| (command.rollback)())
                .collect()
        }
    }

    pub fn add_field() -> String {
        "add field".to_string()
    }

    pub fn remove_field() -> String {
        "remove field".to_string()
    }
}

mod using_fn_trait_objects {
    type Migration<'a> = Box<dyn Fn() -> &'a str>;

    pub struct Schema<'a> {
        executes: Vec<Migration<'a>>,
        rollbacks: Vec<Migration<'a>>,
    }

    impl<'a> Schema<'a> {
        pub fn new() -> Self {
            Self {
                executes: vec![],
                rollbacks: vec![],
            }
        }

        pub fn add_migration<E, R>(&mut self, execute: E, rollback: R)
        where
            E: Fn() -> &'a str + 'static,
            R: Fn() -> &'a str + 'static,
        {
            self.executes.push(Box::new(execute));
            self.rollbacks.push(Box::new(rollback));
        }

        pub fn execute(&self) -> Vec<&str> {
            self.executes.iter().map(|command| command()).collect()
        }

        pub fn rollback(&self) -> Vec<&str> {
            // Commands are rolled back in the reverse direction.
            self.rollbacks
                .iter()
                .rev()
                .map(|command| command())
                .collect()
        }
    }

    pub fn add_field() -> &'static str {
        "add field"
    }

    pub fn remove_field() -> &'static str {
        "remove field"
    }
}

fn main() {
    {
        let mut schema = using_trait_objects::Schema::new();
        let command = Box::new(using_trait_objects::CreateTable);
        schema.add_migration(command);
        let command = Box::new(using_trait_objects::AddField);
        schema.add_migration(command);
        assert_eq!(vec!["create table", "add field"], schema.execute());
        assert_eq!(vec!["remove field", "drop table"], schema.rollback());
    }
    {
        let mut schema = using_function_pointers::Schema::new();
        schema.add_migration(|| "create table".to_string(), || "drop table".to_string());
        schema.add_migration(
            using_function_pointers::add_field,
            using_function_pointers::remove_field,
        );
        assert_eq!(vec!["create table", "add field"], schema.execute());
        assert_eq!(vec!["remove field", "drop table"], schema.rollback());
    }
    {
        let mut schema = using_fn_trait_objects::Schema::new();
        schema.add_migration(|| "create table", || "drop table");
        schema.add_migration(
            using_fn_trait_objects::add_field,
            using_fn_trait_objects::remove_field,
        );
        assert_eq!(vec!["create table", "add field"], schema.execute());
        assert_eq!(vec!["remove field", "drop table"], schema.rollback());
    }
}
